#include "vsf.h"

#ifdef BCMWIFI_SDIO

#define CMD0    0
#define CMD2    2
#define CMD3    3
#define CMD5    5
#define CMD6    6
#define CMD7    7
#define CMD8    8
#define CMD9    9
#define CMD12   12
#define CMD24   24
#define CMD25   25
#define CMD17   17
#define CMD18   18
#define CMD52   52
#define CMD53   53

#define CMD55   55
#define ACMD41  41
#define ACMD51  51
#define ACMD6   6

#define BCMWIFI_SDIO_BLKSIZE			64

enum bcm_bus_sdio_EVT_t
{
	BCM_BUS_SDIO_EVT_TRANSFER_DONE = VSFSM_EVT_USER_LOCAL + 1,
};

uint32_t bcm_bus_sdio_fix_u32(struct bcm_bus_t *bcm_bus, uint32_t value)
{
	return value;
}

static void bcm_bus_sdio_callback(void *param)
{
	struct bcm_bus_t *bcm_bus = param;

	if (bcm_bus->bus_ops_sm != NULL)
	{
		vsfsm_post_evt_pending(bcm_bus->bus_ops_sm, BCM_BUS_SDIO_EVT_TRANSFER_DONE);
	}
}

static vsf_err_t sdio_transfer(struct bcm_bus_sdio_t *sdio_priv,
		uint8_t dir_cmd, uint32_t arg, uint8_t *buf, uint16_t blkcnt,
		uint16_t blksize, uint8_t resp_type)
{
	sdio_priv->trans.cmd = 0;
	sdio_priv->trans.error = 0;
	if (resp_type >= VSFHAL_SDIO_RESP_SHORT)
		sdio_priv->trans.need_resp = 1;
	if (resp_type == VSFHAL_SDIO_RESP_LONG)
		sdio_priv->trans.long_resp = 1;
	sdio_priv->trans.r0w1 = dir_cmd >> 7;
	sdio_priv->trans.block_cnt = blkcnt;
	sdio_priv->trans.block_len = blksize;
	sdio_priv->trans.buffer = buf;
	return vsfhal_sdio_start(sdio_priv->index, dir_cmd & 0x3f, arg, &sdio_priv->trans);
}

#define sdio_cmd_transfer(priv, cmd, arg, resp_type)\
		sdio_transfer(priv, 0x80 | (cmd), arg, NULL, 0, 0, resp_type)

static vsf_err_t bcm_bus_sdio_transact(struct vsfsm_pt_t *pt,
		vsfsm_evt_t evt, uint8_t rw, uint8_t func, uint32_t addr, uint16_t size,
		struct vsf_buffer_t *buffer)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_sdio_t *sdio_priv = &bcm_bus->priv.sdio;
	uint32_t transact_size;

	vsfsm_pt_begin(pt);
	bcm_bus->bus_ops_sm = pt->sm;

	if ((NULL == buffer) || ((buffer->size > 0) && (NULL == buffer->buffer)))
	{
		return VSFERR_FAIL;
	}

	sdio_priv->transfer_retry = 0;
	do
	{
		uint32_t arg;
		
		if (size == 1) // cmd52
		{
			arg = *buffer->buffer | ((addr & 0x1ffff) << 9) |
					((uint32_t)(func & 0x3) << 28) |
					((uint32_t)(rw ? 1 : 0) << 31);
			sdio_cmd_transfer(sdio_priv, (rw << 7) | CMD52, arg, VSFHAL_SDIO_RESP_SHORT);
			vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);
			if (sdio_priv->trans.error == 0)
			{
				*(buffer->buffer) = sdio_priv->trans.resp[0] & 0xff;
				goto end;
			}
		}
		else // cmd53
		{
			uint16_t count;
			uint16_t blksize;

			if ((BCM_CMD_READ == rw) && (size <= 4))
			{
				sdio_priv->transact_buf = &sdio_priv->read_buf;
				transact_size = 8;
			}
			else
			{
				sdio_priv->transact_buf = buffer;
				transact_size = (size + 7) & ~7;
			}

			arg = ((addr & 0x1ffff) << 9) | (0x1ul << 26) |
					((uint32_t)(func & 0x3) << 28) |
					((uint32_t)(rw ? 1 : 0) << 31);

			if (transact_size < BCMWIFI_SDIO_BLKSIZE) // byte mode
			{
				count = 1;
				blksize = transact_size;

				arg += transact_size;
			}
			else
			{
				blksize = BCMWIFI_SDIO_BLKSIZE;
				count = (((transact_size / blksize) & 0x1ff) +
						 	((transact_size % blksize) ? 1 : 0)) & 0x1ff;

				arg |= count | (1ul << 27);
			}

			sdio_priv->trans.error = 0;
			err = sdio_transfer(sdio_priv, (rw << 7) | CMD53, arg,
								sdio_priv->transact_buf->buffer, count,
								blksize, VSFHAL_SDIO_RESP_SHORT);
			if (err == 0)
			{
				vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);
				if (sdio_priv->trans.error == 0)
				{
					if ((BCM_CMD_READ == rw) && (size <= 4))
					{
						memcpy(buffer->buffer, sdio_priv->transact_buf->buffer,
								size);
					}
					goto end;
				}
			}
			else
			{
				vsfsm_pt_delay(pt, 5);
			}
		}
	} while(++sdio_priv->transfer_retry < 3);

	vsfsm_pt_end(pt);
	return VSFERR_FAIL;

end:
	bcm_bus->bus_ops_sm = NULL;
	return VSFERR_NONE;
}

// low-level interrupt will be disabled when triggered
// sdpcm will call enable_int to enable the interrupt
// no nothing if fall edge interrupt
static void bcm_bus_sdio_enable_int(struct bcm_bus_t *bus)
{
	vsfhal_sdio_enable_d1int(bus->port.index);
}

// do nothing for low-level interrupt
// check GPIO for fall-edge interrupt
// always return true for poll(no interrupt)
static bool bcm_bus_sdio_is_int(struct bcm_bus_t *bus)
{
	return false;
}

static void bcm_bus_sdio_fini_int(struct bcm_bus_t *bus)
{
	vsfhal_sdio_config_d1int(bus->port.index, NULL, NULL);
}

static void bcm_bus_sdio_init_int(struct bcm_bus_t *bus,
									void (*callback)(void *param), void *param)
{
	vsfhal_sdio_config_d1int(bus->port.index, callback, param);
}

static vsf_err_t bcm_bus_sdio_f2_avail(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
										uint16_t *size)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;
	uint16_t *ptr16 = bcm_bus->priv.sdio.header;

	vsfsm_pt_begin(pt);

	if (!bcm_bus->receiving)
	{
		// read interrupt flag
		bcm_bus->f2_pt.sm = pt->sm;
		bcm_bus->f2_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->f2_pt, evt,
					BCM_COREREG_SDIOD_INT_STATUS, 4, (uint8_t *)&bcm_bus->intf);
		if (err != 0) return err;

		if (bcm_bus->intf & BCM_COREREG_SDIOD_FRAME_AVAIL)
		{
			bcm_bus->receiving = true;
		}
	}

	if (bcm_bus->receiving)
	{
		// read frame header
		bcm_bus->f2_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_transact(&bcm_bus->f2_pt, evt, BCM_CMD_READ,
							BCM_FUNC_F2, 0, 8, &bcm_bus->priv.sdio.header_buf);
		if (err != 0) return err;

		if (((ptr16[0] | ptr16[1]) == 0) || ((ptr16[0] ^ ptr16[1]) != 0xFFFF))
		{
			ptr16[0] = 0;
		}
	}
	else
	{
		ptr16[0] = 0;
	}

	vsfsm_pt_end(pt);

	*size = *ptr16;
	if (!*size)
	{
		bcm_bus->receiving = false;
	}
	return VSFERR_NONE;
}

static vsf_err_t bcm_bus_sdio_f2_read(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
									uint16_t size, struct vsf_buffer_t *buffer)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_sdio_t *sdio_priv = &bcm_bus->priv.sdio;

	vsfsm_pt_begin(pt);

	// copy buffer header
	((uint32_t *)buffer->buffer)[0] = ((uint32_t *)sdio_priv->header)[0];
	((uint32_t *)buffer->buffer)[1] = ((uint32_t *)sdio_priv->header)[1];
	buffer->buffer += 8;

	bcm_bus->f2_pt.sm = pt->sm;
	bcm_bus->f2_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_transact(&bcm_bus->f2_pt, evt, BCM_CMD_READ, BCM_FUNC_F2,
								0, ((size - 8) + 7) & ~7, buffer);
	if (err != 0) return err;

	buffer->buffer -= 8;
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static vsf_err_t bcm_bus_sdio_f2_abort(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;

	vsfsm_pt_begin(pt);

	bcm_bus->f2_pt.sm = pt->sm;
	bcm_bus->f2_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->f2_pt, evt,
						SDIOD_CCCR_IOABORT, 1, BCM_FUNC_F2);
	if (err != 0) return err;

	bcm_bus->f2_pt.sm = pt->sm;
	bcm_bus->f2_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->f2_pt, evt,
						BCM_BPREG_FRAMECTRL, 1, BCM_BPREG_FRAMECTRL_RF_TERM);
	if (err != 0) return err;

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static vsf_err_t bcm_bus_sdio_enable(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_sdio_t *sdio_priv = &bcm_bus->priv.sdio;

	vsfsm_pt_begin(pt);

	// Enable F1 and F2
	vsfsm_pt_delay(pt, 2);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IOEN, 1, 0x2 | 0x4);
	if (err != 0) goto exit;

	// Enable F2 interrupt only
	vsfsm_pt_delay(pt, 2);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_INTEN, 1, 0x1 | 0x4);
	if (err != 0) goto exit;

	vsfsm_pt_delay(pt, 2);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IORDY, 1, &sdio_priv->temp_byte);
	if (err != 0) goto exit;

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
exit:
	return err;
}

static vsf_err_t bcm_bus_sdio_waitf2(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;

	vsfsm_pt_begin(pt);

	while (1)
	{
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IORDY,
								1, (uint8_t *)&bcm_bus->tmpreg);
		if (err != 0) return err;

		if (bcm_bus->tmpreg & 0x04)
		{
			break;
		}
		vsfsm_pt_delay(pt, 5);
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static uint32_t bcm_bus_sdio_f2size(uint32_t f2size)
{
	if (f2size >= BCMWIFI_SDIO_BLKSIZE)
	{
		f2size -= 8;		// skip header
		f2size = (f2size + BCMWIFI_SDIO_BLKSIZE - 1) & ~(BCMWIFI_SDIO_BLKSIZE - 1);
		f2size += 8;
	}
	return f2size;
}

static vsf_err_t bcm_bus_sdio_init(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_sdio_t *sdio_priv = &bcm_bus->priv.sdio;

	vsfsm_pt_begin(pt);

	bcm_bus->bus_ops_sm = pt->sm;
	bcm_bus->endian = BCM_BUS_ENDIAN_LE;
	bcm_bus->word_length = BCM_BUS_WORDLEN_16;

	memset(sdio_priv, 0, sizeof(struct bcm_bus_sdio_t));
	sdio_priv->header_buf.buffer = (uint8_t *)sdio_priv->header;
	sdio_priv->header_buf.size = sizeof(sdio_priv->header);
	sdio_priv->read_buf.buffer = (uint8_t *)sdio_priv->buff;
	sdio_priv->read_buf.size = sizeof(sdio_priv->buff);

	sdio_priv->index = bcm_bus->port.index;
	vsfhal_sdio_init(sdio_priv->index);
	vsfhal_sdio_config(sdio_priv->index, 400, 1,
			bcm_bus_sdio_callback, bcm_bus);

	//vsfhal_gpio_init(bcm_bus->port.wakeup_port);
	//vsfhal_gpio_config(bcm_bus->port.wakeup_port, bcm_bus->port.wakeup_pin,
	//		GPIO_INPU);

	sdio_priv->logic_retry = 0;
	do
	{
		vsfhal_gpio_set(bcm_bus->port.rst_port, 1 << bcm_bus->port.rst_pin);
		vsfsm_pt_delay(pt, 2);
		vsfhal_gpio_clear(bcm_bus->port.rst_port, 1 << bcm_bus->port.rst_pin);
		vsfsm_pt_delay(pt, 2);
		vsfhal_gpio_set(bcm_bus->port.rst_port, 1 << bcm_bus->port.rst_pin);
		vsfsm_pt_delay(pt, 10);

		sdio_cmd_transfer(sdio_priv, CMD0, 0, VSFHAL_SDIO_RESP_NONE);
		vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);

		vsfsm_pt_delay(pt, 3);
		sdio_cmd_transfer(sdio_priv, CMD5, 0, VSFHAL_SDIO_RESP_SHORT);
		vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);

		vsfsm_pt_delay(pt, 3);
		sdio_cmd_transfer(sdio_priv, CMD3, 0, VSFHAL_SDIO_RESP_SHORT);
		vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);
		if (sdio_priv->trans.error == 0)
			break;
	} while (++sdio_priv->logic_retry < 3);
	if (sdio_priv->logic_retry >= 3)
		goto error;

	// Send CMD7 with the returned RCA to select the card
	vsfsm_pt_delay(pt, 3);
	sdio_cmd_transfer(sdio_priv, CMD7, sdio_priv->trans.resp[0], VSFHAL_SDIO_RESP_SHORT);
	vsfsm_pt_wfe(pt, BCM_BUS_SDIO_EVT_TRANSFER_DONE);

	bcm_bus->port_init_pt.sm = pt->sm;

	// enable function 1
	sdio_priv->logic_retry = 0;
	do
	{
		vsfsm_pt_delay(pt, 3);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IOEN, 1,
				SDIO_FUNC_ENABLE_1);
		if (err != 0) goto exit;

		vsfsm_pt_delay(pt, 3);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IOEN, 1,
				&sdio_priv->temp_byte);
		if (err != 0) goto exit;
	} while ((++sdio_priv->logic_retry < 3) && (sdio_priv->temp_byte != SDIO_FUNC_ENABLE_1));
	if (sdio_priv->temp_byte != SDIO_FUNC_ENABLE_1)
		goto error;

	// Read the bus width and set to 4 bits
	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_BICTRL, 1, &sdio_priv->temp_byte);
	if (err != 0) goto exit;

	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_BICTRL, 1,
			sdio_priv->temp_byte & ~BUS_SD_DATA_WIDTH_MASK | BUS_SD_DATA_WIDTH_4BIT);
	if (err != 0) goto exit;

	sdio_priv->logic_retry = 0;
	do
	{
		vsfsm_pt_delay(pt, 3);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_BLKSIZE_0, 1, BCMWIFI_SDIO_BLKSIZE);
		if (err != 0) goto exit;

		vsfsm_pt_delay(pt, 10);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_BLKSIZE_0, 1, &sdio_priv->temp_byte);
		if (err != 0) goto exit;
	} while ((++sdio_priv->logic_retry < 20) && (sdio_priv->temp_byte != BCMWIFI_SDIO_BLKSIZE));
	if (sdio_priv->temp_byte != BCMWIFI_SDIO_BLKSIZE)
		goto error;

	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_F1BLKSIZE_0, 1, BCMWIFI_SDIO_BLKSIZE);
	if (err != 0) goto exit;

	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_F2BLKSIZE_0, 1, BCMWIFI_SDIO_BLKSIZE);
	if (err != 0) goto exit;

	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_F2BLKSIZE_1, 1, 0);
	if (err != 0) goto exit;

	// Enable/Disable Client interrupts
	vsfsm_pt_delay(pt, 3);
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_INTEN, 1,
			INTR_CTL_MASTER_EN | INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN);
	if (err != 0) goto exit;

	// if we want more than 25 MHz clock
	if (bcm_bus->port.freq_khz > 25000)
	{
		vsfsm_pt_delay(pt, 3);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_SPEED_CONTROL, 1, &sdio_priv->temp_byte);
		if (err != 0) goto exit;

		if (sdio_priv->temp_byte & 0x1)
		{
			vsfsm_pt_delay(pt, 3);
			bcm_bus->port_init_pt.state = 0;
			vsfsm_pt_entry(pt);
			err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_SPEED_CONTROL, 1, sdio_priv->temp_byte | SDIO_SPEED_EHS);
			if (err != 0) goto exit;
		}
		else
		{
			goto error;
		}
	}

	// change clock from 400KHz to user freq, 4-bit mode
	vsfhal_sdio_config(bcm_bus->port.index, bcm_bus->port.freq_khz, 4,
			bcm_bus_sdio_callback, bcm_bus);

	// Wait till the backplane is ready
	sdio_priv->logic_retry = 0;
	do
	{
		vsfsm_pt_delay(pt, 10);
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, SDIOD_CCCR_IORDY, 1, &sdio_priv->temp_byte);
		if (err != 0) goto exit;
	} while ((++sdio_priv->logic_retry < 20) && ((sdio_priv->temp_byte & 0x2) == 0));
	if ((sdio_priv->temp_byte & 0x2) == 0)
		goto error;

	vsfsm_pt_end(pt);
	return VSFERR_NONE;

exit:
	if (err > 0)
		return err;
error:
	return VSFERR_FAIL;
}

const struct bcm_bus_op_t bcm_bus_sdio_op =
{
	bcm_bus_sdio_init, bcm_bus_sdio_enable, bcm_bus_sdio_waitf2, bcm_bus_sdio_f2size,
	bcm_bus_sdio_init_int, bcm_bus_sdio_fini_int,
	bcm_bus_sdio_enable_int, bcm_bus_sdio_is_int,
	bcm_bus_sdio_f2_avail, bcm_bus_sdio_f2_read, bcm_bus_sdio_f2_abort,
	bcm_bus_sdio_fix_u32, bcm_bus_sdio_transact
};

#endif		// BCMWIFI_SDIO
